/**
Copyright (c) 2008 New Gonzo <john@newgonzo.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
**/


package com.newgonzo.cannonball.dom
{
	import flash.events.EventDispatcher;
	import flash.utils.Dictionary;
	
	import com.newgonzo.cannonball.w3c.dom.INode;
	import com.newgonzo.cannonball.w3c.dom.IDocument;
	import com.newgonzo.cannonball.w3c.dom.DocumentPosition;
	
	public class Node extends EventDispatcher implements IExtendedNode
	{	
		private static var instanceCount:uint = 0;
		private var instanceNum:uint;
		
		private var owner:IExtendedDocument;
		private var xml:XML;
	
		private var childCache:Array;
		
		public function Node()
		{
			// used to verify persistence
			instanceNum = instanceCount++;
		
			xml = createDefaultContent();
		}
		
		public function get content():*
		{
			return xml;
		}
		
		public function set content(value:*):void
		{
			if(!(value is XML))
			{
				throw new DOMError("Node.content must be XML");
			}
			
			childCache = null;
			xml = value as XML;
			
			dispatchEvent(new DOMEvent(DOMEvent.CHANGE));
		}
		
		public function set document(value:IExtendedDocument):void
		{
			if(value && owner)
			{
				throw new DOMError("Node.document cannot be set twice.");
			}
			
			owner = value;
			dispatchEvent(new DOMEvent(DOMEvent.CHANGE));
		}
		
		public function get document():IExtendedDocument
		{
			return owner;
		}
		
		public function get nodeValue():String
		{
			return xml.text();
		}
		
		public function set nodeValue(value:String):void
		{
			// TODO: Make this right
			xml.replace("*", value);
		}
		
		public function get nodeName():String
		{
			var name:Object = xml.name();
			
			if(name)
			{
				return name.toString();
			}
			
			return "";
		}
		
		public function get localName():String
		{
			var name:Object = xml.localName();
			
			if(name)
			{
				return name.toString();
			}
			
			return "";
		}
		
		public function get namespaceURI():String
		{
			return (xml.namespace() as Namespace).uri;
		}
		
		public function get prefix():String
		{
			return (xml.namespace() as Namespace).prefix;
		}

		public function get ownerDocument():IDocument
		{
			return owner;
		}
	
		public function get parentNode():INode
		{
			if(!xml.parent())
			{
				return null;
			}
			
			return owner.getElement(xml.parent());
		}
	
		public function get childNodes():Array
		{
			if(childCache)
			{
				return childCache;
			}
			
			childCache = owner.getElements(xml.children());
			return childCache;
		}
		
		public function get firstChild():INode
		{
			var node:XML = xml.child(0)[0] as XML;
			
			if(node)
			{
				return owner.getElement(node);
			}
			
			return null;
		}
		
		public function get lastChild():INode
		{
			var i:uint = xml.children().length();
			
			if(i <= 0)
			{
				return null;
			}
			
			var node:XML = xml.child(i - 1)[0] as XML;
			
			if(node)
			{
				return owner.getElement(node);
			}
			
			return null;
		}
		
		public function get nextSibling():INode
		{
			if(!xml.parent())
			{
				return null;
			}
			
			var node:XML = xml.parent().child(xml.childIndex() + 1)[0] as XML;
			
			if(node)
			{
				return owner.getElement(node);
			}
			
			return null;
		}
		
		public function get previousSibling():INode
		{
			if(!xml.parent())
			{
				return null;
			}
			
			var sib:XML = xml.parent().children()[xml.childIndex() - 1] as XML;
			
			if(sib)
			{
				return  owner.getElement(sib);
			}
			
			return null;
		}
		
		public function get textContent():String
		{
			return xml.text().toString();
		}
		
		public function compareDocumentPosition(node:INode):uint
		{
			return owner.compareNodePositions(this, node);
		}
		
		public function isSameNode(node:INode):Boolean
		{
			return node === this;
		}
		
		public function isEqualNode(node:INode):Boolean
		{
			if(!node) return false;
			
			// compares XML nodes (like strings), not instances
			return (node as IExtendedNode).content.toXMLString().replace(/\s*/g, '') == this.content.toXMLString().replace(/\s*/g, '');
		}
		
		public function get hasAttributes():Boolean
		{
			return xml.attributes().length > 0;
		}
		
		public function get hasChildNodes():Boolean
		{
			return xml.children().length > 0;
		}
		
		protected function createDefaultContent():XML
		{
			return new XML();
		}
		
		protected function formatToString(elemName:String, ...props):String
		{
			// no new lines
			var s:String = "[" + instanceNum + ":" + elemName;
			
			props.push("localName");
			
			if(props.length > 0)
			{
				try
				{
					s += "(";
					var prop:String;
					var a:Array = new Array();
					
					for each(prop in props)
					{
						a.push(prop + "=" + this[prop].toString());
					}
					
					s += a.join(" ") + ")";
				}
				catch(e:Error)
				{
					trace(e);
				}
			}
			
			return s + "]";
		}
	
		override public function toString():String
		{
			return this.formatToString("Node");
		}
	}
}